Parameters for each preprocessing step

Parameters for sc_trim_barcode

File paths

  • input fastq1: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.R2.fastq.gz
  • input fastq2: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.R1.fastq.gz
  • output fastq: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.combined.fastq.gz

Read structure

assume read1 contains the transcript

  • barcode in read1: NA
  • barcode in read2: start at position 6, length 8
  • UMI in read2: start at position 0, length 6

Read filter

  • remove reads that have N in its barcode or UMI: FALSE
  • remove reads with low quality: TRUE
    • minimum read quality: 20
    • maximum number of base below minimum read quality: 2

Parameters for alignment

  • input fastq: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.combined.fastq.gz
  • output bam file: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.combined.subread.bam
  • genome index: /stornext/Projects/score/Analyses/G000396_Danu/extdata/PlasmoDB-51_Pfalciparum3D7/PlasmoDB-51_Pfalciparum3D7_with_ERCC

Parameters for sc_exon_mapping

  • input bam file: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.combined.subread.bam
  • output bam file: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.combined.exon.bam
  • transctiptome annotations: /stornext/Projects/score/Analyses/G000396_Danu/extdata/PlasmoDB-51_Pfalciparum3D7/PlasmoDB-51_Pfalciparum3D7.gff, /stornext/Projects/score/Analyses/G000396_Danu/renv/library/R-4.3/x86_64-pc-linux-gnu/scPipe/extdata/ERCC92_anno.gff3
  • do strand specific mapping: TRUE
  • fix chromosome names: FALSE

Parameters for sc_demultiplex

  • input bam file: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.combined.exon.bam
  • output folder: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43
  • barcode annotation file: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.barcode_annotation.csv
  • maximum mismatch allowed in barcode: 1

Parameters for sc_gene_counting

  • output folder: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43
  • barcode annotation file: /vast/scratch/users/hickey/G000396_Danu/scPipe/RPI-43/RPI-43.barcode_annotation.csv
  • UMI correction: simple correction and merge UMI with distance 1
  • gene filtering: FALSE

Data summary

The organism is “hsapiens_gene_ensembl”, and gene id type is “ensembl_gene_id”.

Overall barcode statistics

if (is.null(params$organism) || is.na(params$organism)) {
  sce = create_sce_by_dir(params$outdir)
} else {
  sce = create_sce_by_dir(params$outdir, organism=params$organism, gene_id_type=params$gene_id_type)
}
overall_stat = demultiplex_info(sce)
datatable(overall_stat, width=800)

Plot barcode match statistics in pie chart:

plot_demultiplex(sce)

Read alignment statistics

ggplotly(plot_mapping(sce, dataname=params$samplename, percentage = FALSE))
ggplotly(plot_mapping(sce, dataname=params$samplename, percentage = TRUE))

Summary and distributions of QC metrics

if (any(colSums(counts(sce)) == 0)) {
  zero_cells = sum(colSums(counts(sce)) == 0)
  sce = sce[, colSums(counts(sce)) > 0]
} else {
  zero_cells = 0
}

Datatable of all QC metrics:

sce = calculate_QC_metrics(sce)
no ERCC spike-in. Skip `non_ERCC_percent`
if(!all(colSums(as.data.frame(QC_metrics(sce)))>0)){
  QC_metrics(sce) = QC_metrics(sce)[, colSums(as.data.frame(QC_metrics(sce)))>0]
}
datatable(as.data.frame(QC_metrics(sce)), width=800, options=list(scrollX= TRUE))

Summary of all QC metrics:

datatable(do.call(cbind, lapply(QC_metrics(sce), summary)), width=800, options=list(scrollX= TRUE))

Number of reads mapped to exon before UMI deduplication VS number of genes detected:

ggplotly(ggplot(as.data.frame(QC_metrics(sce)), aes(x=mapped_to_exon, y=number_of_genes))+geom_point(alpha=0.8))

Quality control

Detect outlier cells

A robustified Mahalanobis Distance is calculated for each cell then outliers are detected based on the distance. However, due to the complex nature of single cell transcriptomes and protocol used, such a method can only be used to assist the quality control process. Visual inspection of the quality control metrics is still required. By default we use comp = 2 and the algorithm will try to separate the quality control metrics into two gaussian clusters.

The number of outliers:

sce_qc = detect_outlier(sce, type="low", comp = 2)
the following QC metrics not found in colData from sce: 'non_mt_percent' and 'non_ERCC_percent'
table(QC_metrics(sce_qc)$outliers)

FALSE  TRUE 
  220   156 

Pairwise plot for QC metrics, colored by outliers:

plot_QC_pairs(sce_qc)

plot highest expression genes

Remove low quality cells and plot highest expression genes.

sce_qc = remove_outliers(sce_qc)
sce_qc = convert_geneid(sce_qc, returns="external_gene_name")
Number of NA in new gene id: 5724. Duplicated id: -0.5
sce_qc <- calculate_QC_metrics(sce_qc)
no ERCC spike-in. Skip `non_ERCC_percent`
plotHighestExprs(sce_qc, n=20)

remove low abundant genes

Plot the average count for each genes:

ave.counts <- rowMeans(counts(sce_qc))
hist(log10(ave.counts), breaks=100, main="", col="grey80",
     xlab=expression(Log[10]~"average count"))

As a loose filter we keep genes that are expressed in at least two cells and for cells that express that gene, the average count larger than two. However this is not the gold standard and the filter may variy depending on the data.

keep1 = (apply(counts(sce_qc), 1, function(x) mean(x[x>0])) > 1.1)  # average count larger than 1.1
keep2 = (rowSums(counts(sce_qc)>0) > 5)  # expressed in at least 5 cells

sce_qc = sce_qc[(keep1 & keep2), ]
dim(sce_qc)
[1] 5587  220

We got 5587 genes left after removing low abundant genes.

Data normalization

Normalization by scran and scater

Compute the normalization size factor

ncells = ncol(sce_qc)
if (ncells > 200) {
  sce_qc <- computeSumFactors(sce_qc)
} else {
  sce_qc <- computeSumFactors(sce_qc, sizes=as.integer(c(ncells/7, ncells/6, ncells/5, ncells/4, ncells/3)))
}
summary(sizeFactors(sce_qc))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.1683  0.4156  0.7546  1.0000  1.2525  5.9488 
if (min(sizeFactors(sce_qc)) <= 0) {
  sce_qc = sce_qc[, sizeFactors(sce_qc)>0]
}

PCA plot using gene expressions as input, colored by the number of genes.

cpm(sce_qc) = calculateCPM(sce_qc, size.factors=NULL)
sce_qc <- runPCA(sce_qc, exprs_values = "cpm")
plotPCA(sce_qc, colour_by="number_of_genes")

Normalize the data using size factor and get high variable genes

The highly variable genes are chosen based on trendVar from scran with FDR > 0.05 and biological variation larger than 0.5. If the number of highly variable genes is smaller than 100 we will select the top 100 genes by biological variation. If the number is larger than 500 we will only keep top 500 genes by biological variation.

sce_qc <- logNormCounts(sce_qc)

var.out <- modelGeneVar(sce_qc)

if (length(which(var.out$FDR <= 0.05 & var.out$bio >= 0.5)) < 500){
  hvg.out <- var.out[order(var.out$bio, decreasing=TRUE)[1:500], ]
}else if(length(which(var.out$FDR <= 0.05 & var.out$bio >= 0.5)) > 1000){
  hvg.out <- var.out[order(var.out$bio, decreasing=TRUE)[1:1000], ]
}else{
  hvg.out <- var.out[which(var.out$FDR <= 0.05 & var.out$bio >= 0.5), ]
  hvg.out <- hvg.out[order(hvg.out$bio, decreasing=TRUE), ]
}

plot(var.out$mean, var.out$total, pch=16, cex=0.6, xlab="Mean log-expression",
     ylab="Variance of log-expression")
o <- order(var.out$mean)
lines(var.out$mean[o], var.out$tech[o], col="dodgerblue", lwd=2)
points(var.out$mean[rownames(var.out) %in% rownames(hvg.out)], var.out$total[rownames(var.out) %in% rownames(hvg.out)], col="red", pch=16)

Heatmap of top100 high variable genes

gene_exp = exprs(sce_qc)

gene_exp = gene_exp[rownames(hvg.out)[1:100], ]

hc.rows <- hclust(dist(gene_exp))
hc.cols <- hclust(dist(t(gene_exp)))

gene_exp = gene_exp[hc.rows$order, hc.cols$order]

m = list(
  l = 100,
  r = 40,
  b = 10,
  t = 10,
  pad = 0
)

plot_ly(
    x = colnames(gene_exp), y = rownames(gene_exp),
    z = gene_exp, type = "heatmap")%>%
layout(autosize = F, margin = m)

Dimensionality reduction using high variable genes

Dimensionality reduction by PCA

sce_qc <- runPCA(sce_qc, exprs_values = "logcounts")
plotPCA(sce_qc, colour_by="number_of_genes")

Dimensionality reduction by t-SNE

set.seed(100)
if (any(duplicated(t(logcounts(sce_qc)[rownames(hvg.out), ])))) {
  sce_qc = sce_qc[, !duplicated(t(logcounts(sce_qc)[rownames(hvg.out), ]))]
}
sce_qc <- runTSNE(sce_qc, exprs_values="logcounts", perplexity=10,feature_set=rownames(hvg.out))
out5 <- plotTSNE(sce_qc, colour_by="number_of_genes") + ggtitle("Perplexity = 10")
sce_qc <- runTSNE(sce_qc, exprs_values="logcounts", perplexity=20,feature_set=rownames(hvg.out))
out10 <- plotTSNE(sce_qc, colour_by="number_of_genes") + ggtitle("Perplexity = 20")
sce_qc <- runTSNE(sce_qc, exprs_values="logcounts", perplexity=30,feature_set=rownames(hvg.out))
out20 <- plotTSNE(sce_qc, colour_by="number_of_genes") + ggtitle("Perplexity = 30")
gridExtra::grid.arrange(out5, out10, out20, ncol = 3)

LS0tCnRpdGxlOiAic2NQaXBlIHJlcG9ydCBmb3Igc2NSTkEtU2VxIHNhbXBsZSBgciBwYXJhbXMkc2FtcGxlbmFtZWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29sbGFwc2VkOiBmYWxzZQpwYXJhbXM6CiAgc2FtcGxlbmFtZTogIlJQSS00MyIKICBmcTE6ICIvdmFzdC9zY3JhdGNoL3VzZXJzL2hpY2tleS9HMDAwMzk2X0RhbnUvc2NQaXBlL1JQSS00My9SUEktNDMuUjIuZmFzdHEuZ3oiCiAgZnEyOiAiL3Zhc3Qvc2NyYXRjaC91c2Vycy9oaWNrZXkvRzAwMDM5Nl9EYW51L3NjUGlwZS9SUEktNDMvUlBJLTQzLlIxLmZhc3RxLmd6IgogIGZxb3V0OiAiL3Zhc3Qvc2NyYXRjaC91c2Vycy9oaWNrZXkvRzAwMDM5Nl9EYW51L3NjUGlwZS9SUEktNDMvUlBJLTQzLmNvbWJpbmVkLmZhc3RxLmd6IgogIGJjMV9pbmZvOiAiTkEiCiAgYmMyX2luZm86ICJzdGFydCBhdCBwb3NpdGlvbiA2LCBsZW5ndGggOCIKICB1bWlfaW5mbzogInN0YXJ0IGF0IHBvc2l0aW9uIDAsIGxlbmd0aCA2IgogIHJtX246IEZBTFNFCiAgcm1fbG93OiBUUlVFCiAgbWluX3E6IDIwCiAgbnVtX2JxOiAyCiAgYmFtX2FsaWduOiAiL3Zhc3Qvc2NyYXRjaC91c2Vycy9oaWNrZXkvRzAwMDM5Nl9EYW51L3NjUGlwZS9SUEktNDMvUlBJLTQzLmNvbWJpbmVkLnN1YnJlYWQuYmFtIgogIGdfaW5kZXg6ICIvc3Rvcm5leHQvUHJvamVjdHMvc2NvcmUvQW5hbHlzZXMvRzAwMDM5Nl9EYW51L2V4dGRhdGEvUGxhc21vREItNTFfUGZhbGNpcGFydW0zRDcvUGxhc21vREItNTFfUGZhbGNpcGFydW0zRDdfd2l0aF9FUkNDIgogIGJhbV9tYXA6ICIvdmFzdC9zY3JhdGNoL3VzZXJzL2hpY2tleS9HMDAwMzk2X0RhbnUvc2NQaXBlL1JQSS00My9SUEktNDMuY29tYmluZWQuZXhvbi5iYW0iCiAgb3V0ZGlyOiAiL3Zhc3Qvc2NyYXRjaC91c2Vycy9oaWNrZXkvRzAwMDM5Nl9EYW51L3NjUGlwZS9SUEktNDMiCiAgYW5ub19nZmY6ICIvc3Rvcm5leHQvUHJvamVjdHMvc2NvcmUvQW5hbHlzZXMvRzAwMDM5Nl9EYW51L2V4dGRhdGEvUGxhc21vREItNTFfUGZhbGNpcGFydW0zRDcvUGxhc21vREItNTFfUGZhbGNpcGFydW0zRDcuZ2ZmLCAvc3Rvcm5leHQvUHJvamVjdHMvc2NvcmUvQW5hbHlzZXMvRzAwMDM5Nl9EYW51L3JlbnYvbGlicmFyeS9SLTQuMy94ODZfNjQtcGMtbGludXgtZ251L3NjUGlwZS9leHRkYXRhL0VSQ0M5Ml9hbm5vLmdmZjMiCiAgc3RuZDogVFJVRQogIGZpeF9jaHI6IEZBTFNFCiAgYmNfYW5ubzogIi92YXN0L3NjcmF0Y2gvdXNlcnMvaGlja2V5L0cwMDAzOTZfRGFudS9zY1BpcGUvUlBJLTQzL1JQSS00My5iYXJjb2RlX2Fubm90YXRpb24uY3N2IgogIG1heF9taXM6IDEKICBVTUlfY29yOiAic2ltcGxlIGNvcnJlY3Rpb24gYW5kIG1lcmdlIFVNSSB3aXRoIGRpc3RhbmNlIDEiCiAgZ2VuZV9mbDogRkFMU0UKICBvcmdhbmlzbTogImhzYXBpZW5zX2dlbmVfZW5zZW1ibCIKICBnZW5lX2lkX3R5cGU6ICJlbnNlbWJsX2dlbmVfaWQiCgotLS0KYGBge3IsIGluY2x1ZGU9RkFMU0V9CmdldFZhbCA8LSBmdW5jdGlvbih4LCBkZWZhdWx0ID0gTlVMTCkgewogIGlmZWxzZShpcy5udWxsKHgpLCBkZWZhdWx0LCB4KQp9CmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KERUKQpsaWJyYXJ5KHNjYXRlcikKbGlicmFyeShzY3JhbikKbGlicmFyeShzY1BpcGUpCmxpYnJhcnkoUnRzbmUpCmBgYAoKIyBQYXJhbWV0ZXJzIGZvciBlYWNoIHByZXByb2Nlc3Npbmcgc3RlcAoKIyMgUGFyYW1ldGVycyBmb3IgYHNjX3RyaW1fYmFyY29kZWAKCiMjIyBGaWxlIHBhdGhzCgoqIGlucHV0IGZhc3RxMTogYHIgcGFyYW1zJGZxMWAKKiBpbnB1dCBmYXN0cTI6IGByIHBhcmFtcyRmcTJgCiogb3V0cHV0IGZhc3RxOiBgciBwYXJhbXMkZnFvdXRgCgojIyMgUmVhZCBzdHJ1Y3R1cmUKCmFzc3VtZSByZWFkMSBjb250YWlucyB0aGUgdHJhbnNjcmlwdAoKKiBiYXJjb2RlIGluIHJlYWQxOiBgciBwYXJhbXMkYmMxX2luZm9gCiogYmFyY29kZSBpbiByZWFkMjogYHIgcGFyYW1zJGJjMl9pbmZvYAoqIFVNSSBpbiByZWFkMjogYHIgcGFyYW1zJHVtaV9pbmZvYAoKIyMjIFJlYWQgZmlsdGVyCgoqIHJlbW92ZSByZWFkcyB0aGF0IGhhdmUgYE5gIGluIGl0cyBiYXJjb2RlIG9yIFVNSTogYHIgcGFyYW1zJHJtX25gCiogcmVtb3ZlIHJlYWRzIHdpdGggbG93IHF1YWxpdHk6IGByIHBhcmFtcyRybV9sb3dgCmByIGlmIChwYXJhbXMkcm1fbG93KXtwYXN0ZSgiXHQqIG1pbmltdW0gcmVhZCBxdWFsaXR5OiIsIHBhcmFtcyRtaW5fcSwgIlxuIiwgIlx0KiBtYXhpbXVtIG51bWJlciBvZiBiYXNlIGJlbG93IG1pbmltdW0KcmVhZCBxdWFsaXR5OiIsIHBhcmFtcyRudW1fYnEsICJcbiIpfWAKCiMjIFBhcmFtZXRlcnMgZm9yIGFsaWdubWVudAoKKiBpbnB1dCBmYXN0cTogYHIgcGFyYW1zJGZxb3V0YAoqIG91dHB1dCBiYW0gZmlsZTogYHIgcGFyYW1zJGJhbV9hbGlnbmAKKiBnZW5vbWUgaW5kZXg6IGByIHBhcmFtcyRnX2luZGV4YAoKIyMgUGFyYW1ldGVycyBmb3IgYHNjX2V4b25fbWFwcGluZ2AKCiogaW5wdXQgYmFtIGZpbGU6IGByIHBhcmFtcyRiYW1fYWxpZ25gCiogb3V0cHV0IGJhbSBmaWxlOiBgciBwYXJhbXMkYmFtX21hcGAKKiB0cmFuc2N0aXB0b21lIGFubm90YXRpb25zOiBgciBwYXJhbXMkYW5ub19nZmZgCiogZG8gc3RyYW5kIHNwZWNpZmljIG1hcHBpbmc6IGByIHBhcmFtcyRzdG5kYAoqIGZpeCBjaHJvbW9zb21lIG5hbWVzOiBgciBwYXJhbXMkZml4X2NocmAKCiMjIFBhcmFtZXRlcnMgZm9yIGBzY19kZW11bHRpcGxleGAKCiogaW5wdXQgYmFtIGZpbGU6IGByIHBhcmFtcyRiYW1fbWFwYAoqIG91dHB1dCBmb2xkZXI6IGByIHBhcmFtcyRvdXRkaXJgCiogYmFyY29kZSBhbm5vdGF0aW9uIGZpbGU6IGByIHBhcmFtcyRiY19hbm5vYAoqIG1heGltdW0gbWlzbWF0Y2ggYWxsb3dlZCBpbiBiYXJjb2RlOiBgciBwYXJhbXMkbWF4X21pc2AKCiMjIFBhcmFtZXRlcnMgZm9yIGBzY19nZW5lX2NvdW50aW5nYAoKKiBvdXRwdXQgZm9sZGVyOiBgciBwYXJhbXMkb3V0ZGlyYAoqIGJhcmNvZGUgYW5ub3RhdGlvbiBmaWxlOiBgciBwYXJhbXMkYmNfYW5ub2AKKiBVTUkgY29ycmVjdGlvbjogYHIgcGFyYW1zJFVNSV9jb3JgCiogZ2VuZSBmaWx0ZXJpbmc6IGByIHBhcmFtcyRnZW5lX2ZsYAoKIyBEYXRhIHN1bW1hcnkKClRoZSBvcmdhbmlzbSBpcyAiYHIgZ2V0VmFsKHBhcmFtcyRvcmdhbmlzbSwgInVua25vd24iKWAiLCBhbmQgZ2VuZSBpZCB0eXBlIGlzICJgciBnZXRWYWwocGFyYW1zJGdlbmVfaWRfdHlwZSwKInVua25vd24iKWAiLgoKIyMgT3ZlcmFsbCBiYXJjb2RlIHN0YXRpc3RpY3MKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQppZiAoaXMubnVsbChwYXJhbXMkb3JnYW5pc20pIHx8IGlzLm5hKHBhcmFtcyRvcmdhbmlzbSkpIHsKICBzY2UgPSBjcmVhdGVfc2NlX2J5X2RpcihwYXJhbXMkb3V0ZGlyKQp9IGVsc2UgewogIHNjZSA9IGNyZWF0ZV9zY2VfYnlfZGlyKHBhcmFtcyRvdXRkaXIsIG9yZ2FuaXNtPXBhcmFtcyRvcmdhbmlzbSwgZ2VuZV9pZF90eXBlPXBhcmFtcyRnZW5lX2lkX3R5cGUpCn0Kb3ZlcmFsbF9zdGF0ID0gZGVtdWx0aXBsZXhfaW5mbyhzY2UpCmRhdGF0YWJsZShvdmVyYWxsX3N0YXQsIHdpZHRoPTgwMCkKYGBgCgpQbG90IGJhcmNvZGUgbWF0Y2ggc3RhdGlzdGljcyBpbiBwaWUgY2hhcnQ6CmBgYHtyfQpwbG90X2RlbXVsdGlwbGV4KHNjZSkKYGBgCgojIyBSZWFkIGFsaWdubWVudCBzdGF0aXN0aWNzCgpgYGB7cn0KZ2dwbG90bHkocGxvdF9tYXBwaW5nKHNjZSwgZGF0YW5hbWU9cGFyYW1zJHNhbXBsZW5hbWUsIHBlcmNlbnRhZ2UgPSBGQUxTRSkpCmBgYAoKYGBge3J9CmdncGxvdGx5KHBsb3RfbWFwcGluZyhzY2UsIGRhdGFuYW1lPXBhcmFtcyRzYW1wbGVuYW1lLCBwZXJjZW50YWdlID0gVFJVRSkpCmBgYAoKIyMgU3VtbWFyeSBhbmQgZGlzdHJpYnV0aW9ucyBvZiBRQyBtZXRyaWNzCgpgYGB7cn0KaWYgKGFueShjb2xTdW1zKGNvdW50cyhzY2UpKSA9PSAwKSkgewogIHplcm9fY2VsbHMgPSBzdW0oY29sU3Vtcyhjb3VudHMoc2NlKSkgPT0gMCkKICBzY2UgPSBzY2VbLCBjb2xTdW1zKGNvdW50cyhzY2UpKSA+IDBdCn0gZWxzZSB7CiAgemVyb19jZWxscyA9IDAKfQpgYGAKCmByIGlmICh6ZXJvX2NlbGxzID4gMCl7cGFzdGUoemVyb19jZWxscywgImNlbGxzIGhhdmUgemVybyByZWFkIGNvdW50cywgcmVtb3ZlIHRoZW0uIil9YAoKRGF0YXRhYmxlIG9mIGFsbCBRQyBtZXRyaWNzOgpgYGB7cn0Kc2NlID0gY2FsY3VsYXRlX1FDX21ldHJpY3Moc2NlKQppZighYWxsKGNvbFN1bXMoYXMuZGF0YS5mcmFtZShRQ19tZXRyaWNzKHNjZSkpKT4wKSl7CiAgUUNfbWV0cmljcyhzY2UpID0gUUNfbWV0cmljcyhzY2UpWywgY29sU3Vtcyhhcy5kYXRhLmZyYW1lKFFDX21ldHJpY3Moc2NlKSkpPjBdCn0KZGF0YXRhYmxlKGFzLmRhdGEuZnJhbWUoUUNfbWV0cmljcyhzY2UpKSwgd2lkdGg9ODAwLCBvcHRpb25zPWxpc3Qoc2Nyb2xsWD0gVFJVRSkpCmBgYAoKU3VtbWFyeSBvZiBhbGwgUUMgbWV0cmljczoKYGBge3J9CmRhdGF0YWJsZShkby5jYWxsKGNiaW5kLCBsYXBwbHkoUUNfbWV0cmljcyhzY2UpLCBzdW1tYXJ5KSksIHdpZHRoPTgwMCwgb3B0aW9ucz1saXN0KHNjcm9sbFg9IFRSVUUpKQpgYGAKCk51bWJlciBvZiByZWFkcyBtYXBwZWQgdG8gZXhvbiBiZWZvcmUgVU1JIGRlZHVwbGljYXRpb24gVlMgbnVtYmVyIG9mIGdlbmVzIGRldGVjdGVkOgpgYGB7cn0KZ2dwbG90bHkoZ2dwbG90KGFzLmRhdGEuZnJhbWUoUUNfbWV0cmljcyhzY2UpKSwgYWVzKHg9bWFwcGVkX3RvX2V4b24sIHk9bnVtYmVyX29mX2dlbmVzKSkrZ2VvbV9wb2ludChhbHBoYT0wLjgpKQpgYGAKCiMgUXVhbGl0eSBjb250cm9sCgojIyBEZXRlY3Qgb3V0bGllciBjZWxscwoKQSByb2J1c3RpZmllZCBNYWhhbGFub2JpcyBEaXN0YW5jZSBpcyBjYWxjdWxhdGVkIGZvciBlYWNoIGNlbGwgdGhlbiBvdXRsaWVycyBhcmUgZGV0ZWN0ZWQgYmFzZWQgb24gdGhlIGRpc3RhbmNlLgpIb3dldmVyLCBkdWUgdG8gdGhlIGNvbXBsZXggbmF0dXJlIG9mIHNpbmdsZSBjZWxsIHRyYW5zY3JpcHRvbWVzIGFuZCBwcm90b2NvbCB1c2VkLCBzdWNoIGEgbWV0aG9kIGNhbiBvbmx5IGJlIHVzZWQgdG8KYXNzaXN0IHRoZSBxdWFsaXR5IGNvbnRyb2wgcHJvY2Vzcy4gVmlzdWFsIGluc3BlY3Rpb24gb2YgdGhlIHF1YWxpdHkgY29udHJvbCBtZXRyaWNzIGlzIHN0aWxsIHJlcXVpcmVkLiBCeSBkZWZhdWx0IHdlCnVzZSBgY29tcCA9IDJgIGFuZCB0aGUgYWxnb3JpdGhtIHdpbGwgdHJ5IHRvIHNlcGFyYXRlIHRoZSBxdWFsaXR5IGNvbnRyb2wgbWV0cmljcyBpbnRvIHR3byBnYXVzc2lhbiBjbHVzdGVycy4KClRoZSBudW1iZXIgb2Ygb3V0bGllcnM6CmBgYHtyfQpzY2VfcWMgPSBkZXRlY3Rfb3V0bGllcihzY2UsIHR5cGU9ImxvdyIsIGNvbXAgPSAyKQp0YWJsZShRQ19tZXRyaWNzKHNjZV9xYykkb3V0bGllcnMpCmBgYAoKUGFpcndpc2UgcGxvdCBmb3IgUUMgbWV0cmljcywgY29sb3JlZCBieSBvdXRsaWVyczoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnBsb3RfUUNfcGFpcnMoc2NlX3FjKQpgYGAKCiMjIHBsb3QgaGlnaGVzdCBleHByZXNzaW9uIGdlbmVzCgpSZW1vdmUgbG93IHF1YWxpdHkgY2VsbHMgYW5kIHBsb3QgaGlnaGVzdCBleHByZXNzaW9uIGdlbmVzLgoKYGBge3J9CnNjZV9xYyA9IHJlbW92ZV9vdXRsaWVycyhzY2VfcWMpCnNjZV9xYyA9IGNvbnZlcnRfZ2VuZWlkKHNjZV9xYywgcmV0dXJucz0iZXh0ZXJuYWxfZ2VuZV9uYW1lIikKCnNjZV9xYyA8LSBjYWxjdWxhdGVfUUNfbWV0cmljcyhzY2VfcWMpCnBsb3RIaWdoZXN0RXhwcnMoc2NlX3FjLCBuPTIwKQpgYGAKCiMjIHJlbW92ZSBsb3cgYWJ1bmRhbnQgZ2VuZXMKClBsb3QgdGhlIGF2ZXJhZ2UgY291bnQgZm9yIGVhY2ggZ2VuZXM6CmBgYHtyfQphdmUuY291bnRzIDwtIHJvd01lYW5zKGNvdW50cyhzY2VfcWMpKQpoaXN0KGxvZzEwKGF2ZS5jb3VudHMpLCBicmVha3M9MTAwLCBtYWluPSIiLCBjb2w9ImdyZXk4MCIsCiAgICAgeGxhYj1leHByZXNzaW9uKExvZ1sxMF1+ImF2ZXJhZ2UgY291bnQiKSkKYGBgCgpBcyBhIGxvb3NlIGZpbHRlciB3ZSBrZWVwIGdlbmVzIHRoYXQgYXJlIGV4cHJlc3NlZCBpbiBhdCBsZWFzdCB0d28gY2VsbHMgYW5kIGZvciBjZWxscyB0aGF0IGV4cHJlc3MgdGhhdCBnZW5lLCB0aGUKYXZlcmFnZSBjb3VudCBsYXJnZXIgdGhhbiB0d28uIEhvd2V2ZXIgdGhpcyBpcyBub3QgdGhlIGdvbGQgc3RhbmRhcmQgYW5kIHRoZSBmaWx0ZXIgbWF5IHZhcml5IGRlcGVuZGluZyBvbiB0aGUgZGF0YS4KCmBgYHtyfQprZWVwMSA9IChhcHBseShjb3VudHMoc2NlX3FjKSwgMSwgZnVuY3Rpb24oeCkgbWVhbih4W3g+MF0pKSA+IDEuMSkgICMgYXZlcmFnZSBjb3VudCBsYXJnZXIgdGhhbiAxLjEKa2VlcDIgPSAocm93U3Vtcyhjb3VudHMoc2NlX3FjKT4wKSA+IDUpICAjIGV4cHJlc3NlZCBpbiBhdCBsZWFzdCA1IGNlbGxzCgpzY2VfcWMgPSBzY2VfcWNbKGtlZXAxICYga2VlcDIpLCBdCmRpbShzY2VfcWMpCmBgYAoKV2UgZ290IGByIG5yb3coc2NlX3FjKWAgZ2VuZXMgbGVmdCBhZnRlciByZW1vdmluZyBsb3cgYWJ1bmRhbnQgZ2VuZXMuCgojIERhdGEgbm9ybWFsaXphdGlvbgoKIyMgTm9ybWFsaXphdGlvbiBieSBgc2NyYW5gIGFuZCBgc2NhdGVyYAoKQ29tcHV0ZSB0aGUgbm9ybWFsaXphdGlvbiBzaXplIGZhY3RvcgoKYGBge3J9Cm5jZWxscyA9IG5jb2woc2NlX3FjKQppZiAobmNlbGxzID4gMjAwKSB7CiAgc2NlX3FjIDwtIGNvbXB1dGVTdW1GYWN0b3JzKHNjZV9xYykKfSBlbHNlIHsKICBzY2VfcWMgPC0gY29tcHV0ZVN1bUZhY3RvcnMoc2NlX3FjLCBzaXplcz1hcy5pbnRlZ2VyKGMobmNlbGxzLzcsIG5jZWxscy82LCBuY2VsbHMvNSwgbmNlbGxzLzQsIG5jZWxscy8zKSkpCn0Kc3VtbWFyeShzaXplRmFjdG9ycyhzY2VfcWMpKQpgYGAKCmByIGlmIChtaW4oc2l6ZUZhY3RvcnMoc2NlX3FjKSkgPD0gMCl7cGFzdGUoIldlIGhhdmUgbmVnYXRpdmUgc2l6ZSBmYWN0b3JzIGluIHRoZSBkYXRhLiBUaGV5IGluZGljYXRlIGxvdyBxdWFsaXR5IGNlbGxzCmFuZCB3ZSBoYXZlIHJlbW92ZWQgdGhlbS4gVG8gYXZvaWQgbmVnYXRpdmUgc2l6ZSBmYWN0b3JzLCB0aGUgYmVzdCBzb2x1dGlvbiBpcyB0byBpbmNyZWFzZSB0aGUgc3RyaW5nZW5jeSBvZiB0aGUKZmlsdGVyaW5nLiIpfWAKCmBgYHtyfQppZiAobWluKHNpemVGYWN0b3JzKHNjZV9xYykpIDw9IDApIHsKICBzY2VfcWMgPSBzY2VfcWNbLCBzaXplRmFjdG9ycyhzY2VfcWMpPjBdCn0KYGBgCgpQQ0EgcGxvdCB1c2luZyBnZW5lIGV4cHJlc3Npb25zIGFzIGlucHV0LCBjb2xvcmVkIGJ5IHRoZSBudW1iZXIgb2YgZ2VuZXMuCgpgYGB7cn0KY3BtKHNjZV9xYykgPSBjYWxjdWxhdGVDUE0oc2NlX3FjLCBzaXplLmZhY3RvcnM9TlVMTCkKc2NlX3FjIDwtIHJ1blBDQShzY2VfcWMsIGV4cHJzX3ZhbHVlcyA9ICJjcG0iKQpwbG90UENBKHNjZV9xYywgY29sb3VyX2J5PSJudW1iZXJfb2ZfZ2VuZXMiKQpgYGAKCiMjIyBOb3JtYWxpemUgdGhlIGRhdGEgdXNpbmcgc2l6ZSBmYWN0b3IgYW5kIGdldCBoaWdoIHZhcmlhYmxlIGdlbmVzCgpUaGUgaGlnaGx5IHZhcmlhYmxlIGdlbmVzIGFyZSBjaG9zZW4gYmFzZWQgb24gYHRyZW5kVmFyYCBmcm9tIGBzY3JhbmAgd2l0aCBgRkRSID4gMC4wNWAgYW5kIGJpb2xvZ2ljYWwgdmFyaWF0aW9uIGxhcmdlcgp0aGFuIGAwLjVgLiBJZiB0aGUgbnVtYmVyIG9mIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyBpcyBzbWFsbGVyIHRoYW4gMTAwIHdlIHdpbGwgc2VsZWN0IHRoZSB0b3AgMTAwIGdlbmVzIGJ5IGJpb2xvZ2ljYWwKdmFyaWF0aW9uLiBJZiB0aGUgbnVtYmVyIGlzIGxhcmdlciB0aGFuIDUwMCB3ZSB3aWxsIG9ubHkga2VlcCB0b3AgNTAwIGdlbmVzIGJ5IGJpb2xvZ2ljYWwgdmFyaWF0aW9uLgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnNjZV9xYyA8LSBsb2dOb3JtQ291bnRzKHNjZV9xYykKCnZhci5vdXQgPC0gbW9kZWxHZW5lVmFyKHNjZV9xYykKCmlmIChsZW5ndGgod2hpY2godmFyLm91dCRGRFIgPD0gMC4wNSAmIHZhci5vdXQkYmlvID49IDAuNSkpIDwgNTAwKXsKICBodmcub3V0IDwtIHZhci5vdXRbb3JkZXIodmFyLm91dCRiaW8sIGRlY3JlYXNpbmc9VFJVRSlbMTo1MDBdLCBdCn1lbHNlIGlmKGxlbmd0aCh3aGljaCh2YXIub3V0JEZEUiA8PSAwLjA1ICYgdmFyLm91dCRiaW8gPj0gMC41KSkgPiAxMDAwKXsKICBodmcub3V0IDwtIHZhci5vdXRbb3JkZXIodmFyLm91dCRiaW8sIGRlY3JlYXNpbmc9VFJVRSlbMToxMDAwXSwgXQp9ZWxzZXsKICBodmcub3V0IDwtIHZhci5vdXRbd2hpY2godmFyLm91dCRGRFIgPD0gMC4wNSAmIHZhci5vdXQkYmlvID49IDAuNSksIF0KICBodmcub3V0IDwtIGh2Zy5vdXRbb3JkZXIoaHZnLm91dCRiaW8sIGRlY3JlYXNpbmc9VFJVRSksIF0KfQoKcGxvdCh2YXIub3V0JG1lYW4sIHZhci5vdXQkdG90YWwsIHBjaD0xNiwgY2V4PTAuNiwgeGxhYj0iTWVhbiBsb2ctZXhwcmVzc2lvbiIsCiAgICAgeWxhYj0iVmFyaWFuY2Ugb2YgbG9nLWV4cHJlc3Npb24iKQpvIDwtIG9yZGVyKHZhci5vdXQkbWVhbikKbGluZXModmFyLm91dCRtZWFuW29dLCB2YXIub3V0JHRlY2hbb10sIGNvbD0iZG9kZ2VyYmx1ZSIsIGx3ZD0yKQpwb2ludHModmFyLm91dCRtZWFuW3Jvd25hbWVzKHZhci5vdXQpICVpbiUgcm93bmFtZXMoaHZnLm91dCldLCB2YXIub3V0JHRvdGFsW3Jvd25hbWVzKHZhci5vdXQpICVpbiUgcm93bmFtZXMoaHZnLm91dCldLCBjb2w9InJlZCIsIHBjaD0xNikKYGBgCgojIyBIZWF0bWFwIG9mIHRvcDEwMCBoaWdoIHZhcmlhYmxlIGdlbmVzCgpgYGB7cn0KZ2VuZV9leHAgPSBleHBycyhzY2VfcWMpCgpnZW5lX2V4cCA9IGdlbmVfZXhwW3Jvd25hbWVzKGh2Zy5vdXQpWzE6MTAwXSwgXQoKaGMucm93cyA8LSBoY2x1c3QoZGlzdChnZW5lX2V4cCkpCmhjLmNvbHMgPC0gaGNsdXN0KGRpc3QodChnZW5lX2V4cCkpKQoKZ2VuZV9leHAgPSBnZW5lX2V4cFtoYy5yb3dzJG9yZGVyLCBoYy5jb2xzJG9yZGVyXQoKbSA9IGxpc3QoCiAgbCA9IDEwMCwKICByID0gNDAsCiAgYiA9IDEwLAogIHQgPSAxMCwKICBwYWQgPSAwCikKCnBsb3RfbHkoCiAgICB4ID0gY29sbmFtZXMoZ2VuZV9leHApLCB5ID0gcm93bmFtZXMoZ2VuZV9leHApLAogICAgeiA9IGdlbmVfZXhwLCB0eXBlID0gImhlYXRtYXAiKSU+JQpsYXlvdXQoYXV0b3NpemUgPSBGLCBtYXJnaW4gPSBtKQpgYGAKCiMgRGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHVzaW5nIGhpZ2ggdmFyaWFibGUgZ2VuZXMKCiMjIERpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBieSBQQ0EKCmBgYHtyfQpzY2VfcWMgPC0gcnVuUENBKHNjZV9xYywgZXhwcnNfdmFsdWVzID0gImxvZ2NvdW50cyIpCnBsb3RQQ0Eoc2NlX3FjLCBjb2xvdXJfYnk9Im51bWJlcl9vZl9nZW5lcyIpCmBgYAoKIyMgRGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIGJ5IHQtU05FCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTMuNX0Kc2V0LnNlZWQoMTAwKQppZiAoYW55KGR1cGxpY2F0ZWQodChsb2djb3VudHMoc2NlX3FjKVtyb3duYW1lcyhodmcub3V0KSwgXSkpKSkgewogIHNjZV9xYyA9IHNjZV9xY1ssICFkdXBsaWNhdGVkKHQobG9nY291bnRzKHNjZV9xYylbcm93bmFtZXMoaHZnLm91dCksIF0pKV0KfQpzY2VfcWMgPC0gcnVuVFNORShzY2VfcWMsIGV4cHJzX3ZhbHVlcz0ibG9nY291bnRzIiwgcGVycGxleGl0eT0xMCxmZWF0dXJlX3NldD1yb3duYW1lcyhodmcub3V0KSkKb3V0NSA8LSBwbG90VFNORShzY2VfcWMsIGNvbG91cl9ieT0ibnVtYmVyX29mX2dlbmVzIikgKyBnZ3RpdGxlKCJQZXJwbGV4aXR5ID0gMTAiKQpzY2VfcWMgPC0gcnVuVFNORShzY2VfcWMsIGV4cHJzX3ZhbHVlcz0ibG9nY291bnRzIiwgcGVycGxleGl0eT0yMCxmZWF0dXJlX3NldD1yb3duYW1lcyhodmcub3V0KSkKb3V0MTAgPC0gcGxvdFRTTkUoc2NlX3FjLCBjb2xvdXJfYnk9Im51bWJlcl9vZl9nZW5lcyIpICsgZ2d0aXRsZSgiUGVycGxleGl0eSA9IDIwIikKc2NlX3FjIDwtIHJ1blRTTkUoc2NlX3FjLCBleHByc192YWx1ZXM9ImxvZ2NvdW50cyIsIHBlcnBsZXhpdHk9MzAsZmVhdHVyZV9zZXQ9cm93bmFtZXMoaHZnLm91dCkpCm91dDIwIDwtIHBsb3RUU05FKHNjZV9xYywgY29sb3VyX2J5PSJudW1iZXJfb2ZfZ2VuZXMiKSArIGdndGl0bGUoIlBlcnBsZXhpdHkgPSAzMCIpCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKG91dDUsIG91dDEwLCBvdXQyMCwgbmNvbCA9IDMpCmBgYAo=